#!/usr/bin/env python3
# coding:utf-8
import sys
import getopt
import asyncio
import threading
import struct
import time
import random
from enum import IntEnum
from dataclasses import dataclass
import crcmod
from loguru import logger
from nobloack_key_input import Noblock_terminal

Server_IP = "127.0.0.1"
Server_Port = 9966
Robot_ID = 3752
Cmd_Control = True


class E_FrameHead(IntEnum):
    TcpServer_Id: int = 0xBB
    Car_Id: int = 0xAA
    Car_Cmd_Id: int = 0xAB


class E_FrameCmd(IntEnum):
    Control: int = 0x02
    HeartBeat: int = 0x03
    Activate: int = 0x80
    RespLogIn: int = 0xF0


class E_DeviceCmd(IntEnum):
    LogIn: int = 0x10
    RealTimePackage: int = 0x1
    RealTimePackage_V2: int = 0xA1
    Heartbeat: int = 0x2
    Heartbeat_V2: int = 0xA2


def show_hex(val, header=""):
    if header:
        print(header, "---------")

    cnt = 0
    for i in val:
        print(f"{i:02X}", end=" ")
        cnt += 1
        if cnt % 16 == 0:
            print("--\r")


@dataclass
class Frame:
    header: int = 0xAA
    payload_len: int = 0
    client_id: int = 0
    timestamp: int = 0
    cmd: int = 0
    data: bytes = b""
    crc16: int = 0

    def to_bytes(self, crc16_fun) -> bytes:
        self.timestamp = int(time.time())
        self.payload_len = 9 + len(self.data)
        data = struct.pack(
            f"<IIB{len(self.data)}s",
            self.client_id,
            self.timestamp,
            self.cmd,
            self.data,
        )
        data = struct.pack("<H", self.payload_len) + data
        self.crc16 = crc16_fun(data)
        frame = struct.pack(
            f"<BHIIB{len(self.data)}sH",
            self.header,
            self.payload_len,
            self.client_id,
            self.timestamp,
            self.cmd,
            self.data,
            self.crc16,
        )
        return frame


class Sim_MushinyF50A_AGV:
    """
    牧星F50A 潜伏车
    """

    def __init__(self, config):
        self.config = config
        self.init_data()

    def init_data(self):
        self.cliend_id = 0x0102
        self.address_id = random.randint(1, 255)
        self.shelf_id = random.randint(1, 255)
        # modbus模式
        self.crc16_fun = crcmod.mkCrcFun(
            0x18005, rev=True, initCrc=0xFFFF, xorOut=0x0000
        )

    async def loop_run(self):
        """启动异步 TCP 服务端"""
        host = self.config["host"]
        port = self.config["port"]
        reader, writer = await asyncio.open_connection(host, port)
        transport = writer.get_extra_info("transport")
        self.reader = reader
        self.writer = writer
        self.event_loop = asyncio.get_running_loop()
        while True:
            try:
                # 1.接收
                recv = await reader.read(1024)
                # 2.检查连接是否关闭
                if not recv:
                    print("socket 连接已关闭")
                    break
                # 3.处理
                self.handler_recv_msg(recv, writer)
            except asyncio.IncompleteReadError:
                print("服务端已断开连接")
                break

        print("Close the connection")
        writer.close()
        await writer.wait_closed()

    async def handle_client(
        self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter
    ) -> None:
        """处理客户端连接"""
        # 获取客户端地址
        client_addr = writer.get_extra_info("peername")
        print(f"新连接来自 {client_addr}")
        # self.Client_Set.add(writer)

        try:
            while True:
                # 读取客户端发送的数据 (最多 1024 字节)
                data = await reader.read(1024)
                if not data:  # 客户端关闭连接
                    break
                print("\r\n recv data:----- \r")
                show_hex(data)
                self.handler_device_msg(data, writer)

        except asyncio.CancelledError:
            print(f"客户端 {client_addr} 连接被取消")
        except Exception as e:
            print(f"处理客户端 {client_addr} 时发生错误: {e}")
        finally:
            # 关闭连接
            # self.Client_Set.remove(writer)
            for key, value in self.Client_Set:
                if value == writer:
                    self.Client_Set.pop(key)
            writer.close()
            await writer.wait_closed()
            print(f"客户端 {client_addr} 连接已关闭")

    def handler_recv_msg(self, recv, socket):
        head, frame_len, client_id, timestamp, cmd = struct.unpack_from("<BHIIB", recv)
        if head != E_FrameHead.TcpServer_Id:
            print(f"非tcpserver通信协议 帧头: {head:2X}")
            return

        if cmd == E_FrameCmd.Control:
            print("收到控制指令")
        elif cmd == E_FrameCmd.HeartBeat:
            print("收到心跳指令")
        elif cmd == E_FrameCmd.Activate:
            print("收到激活指令")
        elif cmd == E_FrameCmd.RespLogIn:
            print("收到登录响应")
        else:
            print("未知指令")

    async def send_data(self, send_data):
        writer = self.writer
        writer.write(send_data)
        await writer.drain()

    def sim_send_login(self):
        cur_time = int(time.time())
        password = 12345678
        device_type = 0xF0
        handware_version = 0x1011
        manufacturing_data = cur_time
        bootuptime = 100
        last_repair_time = cur_time
        battery_soc = random.randint(30, 100)
        address_id = self.address_id
        address_offset_x = random.randint(1, 100)
        address_offset_y = random.randint(1, 100)
        address_offset_z = round(random.uniform(-3.14, 3.14), 1)
        shelf_id = self.shelf_id
        shelf_offset_x = random.randint(1, 100)
        shelf_offset_y = random.randint(1, 100)
        shelf_offset_z = round(random.uniform(-3.14, 3.14), 1)
        bind_shelf_id = self.shelf_id
        status = 0x7A
        cur_status = 0x5B
        fault_code = 0x00
        signal_quality = 10
        intrusion_detection = random.randint(0, 10)
        soft_version = 0x123456
        releast_data = cur_time
        battery_type = 0x1
        battery_max_current = 0x10

        data = struct.pack(
            "<IBHIIIh",
            password,
            device_type,
            handware_version,
            manufacturing_data,
            bootuptime,
            last_repair_time,
            battery_soc,
        )
        data += struct.pack(
            "<IhhfIhhfIBB",
            address_id,
            address_offset_x,
            address_offset_y,
            address_offset_z,
            shelf_id,
            shelf_offset_x,
            shelf_offset_y,
            shelf_offset_z,
            bind_shelf_id,
            status,
            cur_status,
        )
        data += struct.pack(
            "<HBHIiBH",
            fault_code,
            signal_quality,
            intrusion_detection,
            soft_version,
            releast_data,
            battery_type,
            battery_max_current,
        )
        frame = Frame(client_id=self.cliend_id, cmd=E_DeviceCmd.LogIn, data=data)
        send_data = frame.to_bytes(self.crc16_fun)
        # 协程 跨线程发送数据
        asyncio.run_coroutine_threadsafe(self.send_data(send_data), self.event_loop)

    def sim_send_heartbeat(self):
        address_id = self.address_id
        data = struct.pack("<I", address_id)
        frame = Frame(client_id=self.cliend_id, cmd=E_DeviceCmd.Heartbeat, data=data)
        send_data = frame.to_bytes(self.crc16_fun)
        show_hex(send_data, "发送heartbeat")
        # 协程 跨线程发送数据
        asyncio.run_coroutine_threadsafe(self.send_data(send_data), self.event_loop)

    def sim_send_realtimeInfo(self):
        address_id = self.address_id
        address_offset_x = random.randint(1, 100)
        address_offset_y = random.randint(1, 100)
        address_offset_z = round(random.uniform(-3.14, 3.14), 1)
        shelf_id = self.shelf_id
        shelf_offset_x = random.randint(1, 100)
        shelf_offset_y = random.randint(1, 100)
        shelf_offset_z = round(random.uniform(-3.14, 3.14), 1)
        bind_shelf_id = self.shelf_id
        status = 0x7A
        cur_status = 0x5B
        data = struct.pack(
            "<IhhfIhhfIBB",
            address_id,
            address_offset_x,
            address_offset_y,
            address_offset_z,
            shelf_id,
            shelf_offset_x,
            shelf_offset_y,
            shelf_offset_z,
            bind_shelf_id,
            status,
            cur_status,
        )
        frame = Frame(
            client_id=self.cliend_id, cmd=E_DeviceCmd.RealTimePackage, data=data
        )
        send_data = frame.to_bytes(self.crc16_fun)
        show_hex(
            send_data,
            f"发送 realtimeInfo, 二维码地址:{address_id}, 货架地址:{shelf_id}",
        )
        # 协程 跨线程发送数据
        asyncio.run_coroutine_threadsafe(self.send_data(send_data), self.event_loop)


def control_cmd(AGV_impl):
    """
    控制命令函数，提供交互式命令行界面
    用户可以通过输入不同的命令来查看客户端列表、获取帮助或退出程序
    """
    help_str = """\r\n
l: 发送登录 2:心跳 3:实时位置
二维码地址 i +1, k -1
货架地址 u +1, j -1

h帮助, q 退出
"""
    print(help_str)
    noblock_term = Noblock_terminal()
    count = 0
    while True:
        # key = noblock_term.select_cmd(0.2)
        key = noblock_term.noblock_get_cmd()
        if key == "1":
            AGV_impl.sim_send_login()
        elif key == "2":
            AGV_impl.sim_send_heartbeat()
        elif key == "3":
            AGV_impl.sim_send_realtimeInfo()
        elif key == "i":
            AGV_impl.address_id += 1
        elif key == "k":
            AGV_impl.address_id -= 1
        elif key == "u":
            AGV_impl.shelf_id += 1
        elif key == "j":
            AGV_impl.shelf_id -= 1
        elif key == "h":
            print(help_str)
        elif key == "q":
            break

        if count % 100 == 0:
            # 定时打印 使用帮助
            print(help_str)
        count += 1

    print("退出 控制")
    noblock_term.stop_no_block()
    sys.exit(0)


def parse_args():
    try:
        opts, args = getopt.getopt(sys.argv[1:], "p:h:", ["port=", "host=", "id="])
    except getopt.GetoptError as err:
        # print help information and exit:
        print(err)  # will print something like "option -a not recognized"
        sys.exit(2)
    port = Server_Port
    host = Server_IP
    host_id = 0x1234
    global Robot_ID
    for o, a in opts:
        if o in ("-p", "--port"):
            port = int(a)
        elif o in ("-h", "--host"):
            host = a
        elif o in ("--id"):
            Robot_ID = int(a)
        else:
            assert False, "unhandled option"

    config = {"host": host, "port": port, "host_id": host_id}
    return config


def main():
    config = parse_args()
    AGV_impl = Sim_MushinyF50A_AGV(config)
    # Test:命令控制
    if Cmd_Control:
        t1 = threading.Thread(target=control_cmd, args=[AGV_impl], daemon=True)
        t1.start()

    try:
        asyncio.run(AGV_impl.loop_run())
    except KeyboardInterrupt:
        print("服务器已关闭")


if __name__ == "__main__":
    main()
